/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 * USA.
 *
 * =======================================================================
 *
 * This file implements the console
 *
 * =======================================================================
 */

#include "header/client.h"
#include <time.h>

console_t	con;
cvar_t		*con_notifytime;

#define			MAXCMDLINE	256

extern	char	key_lines[32][MAXCMDLINE];
extern	int		edit_line;
extern	int		key_linepos;

void DrawString (int x, int y, char *s) {
	while (*s) {
		re.DrawChar (x, y, *s);
		x+=8;
		s++;
	}
}

void DrawAltString (int x, int y, char *s) {
	while (*s) {
		re.DrawChar (x, y, *s ^ 0x80);
		x+=8;
		s++;
	}
}

void Key_ClearTyping (void) {
	key_lines[edit_line][1] = 0; /* clear any typing */
	key_linepos = 1;
}

void Con_ToggleConsole_f (void) {
	SCR_EndLoadingPlaque (); /* get rid of loading plaque */

	if (cl.attractloop) {
		Cvar_SetValue("windowed_mouse", 0);
		Cbuf_AddText("killserver\n");
		return;
	}

	if (cls.state == ca_disconnected) {
		/* start the demo loop again */
		Cvar_SetValue("windowed_mouse", 1);
		Cbuf_AddText ("d1\n");
		return;
	}

	Key_ClearTyping ();
	Con_ClearNotify ();

	if (cls.key_dest == key_console) {
		M_ForceMenuOff ();
		Cvar_Set ("paused", "0");

	} else {
		M_ForceMenuOff ();
		cls.key_dest = key_console;

		if (Cvar_VariableValue ("maxclients") == 1
		        && Com_ServerState ())
			Cvar_Set ("paused", "1");
	}
}

void Con_ToggleChat_f (void) {
	Key_ClearTyping ();

	if (cls.key_dest == key_console) {
		if (cls.state == ca_active) {
			M_ForceMenuOff ();
			cls.key_dest = key_game;
		}

	} else
		cls.key_dest = key_console;

	Con_ClearNotify ();
}

void Con_Clear_f (void) {
	memset (con.text, ' ', CON_TEXTSIZE);
}

/*
 * Save the console contents out to a file
 */
void Con_Dump_f (void) {
	int		l, x;
	char	*line;
	FILE	*f;
	char	buffer[1024];
	char	name[MAX_OSPATH];

	if (Cmd_Argc() != 2) {
		Com_Printf ("usage: condump <filename>\n");
		return;
	}

	Com_sprintf (name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1));

	Com_Printf ("Dumped console text to %s.\n", name);
	FS_CreatePath (name);
	f = fopen (name, "w");

	if (!f) {
		Com_Printf ("ERROR: couldn't open.\n");
		return;
	}

	/* skip empty lines */
	for (l = con.current - con.totallines + 1 ; l <= con.current ; l++) {
		line = con.text + (l%con.totallines)*con.linewidth;

		for (x=0 ; x<con.linewidth ; x++)
			if (line[x] != ' ')
				break;

		if (x != con.linewidth)
			break;
	}

	/* write the remaining lines */
	buffer[con.linewidth] = 0;

	for ( ; l <= con.current ; l++) {
		line = con.text + (l%con.totallines)*con.linewidth;
		strncpy (buffer, line, con.linewidth);

		for (x=con.linewidth-1 ; x>=0 ; x--) {
			if (buffer[x] == ' ')
				buffer[x] = 0;

			else
				break;
		}

		for (x=0; buffer[x]; x++)
			buffer[x] &= 0x7f;

		fprintf (f, "%s\n", buffer);
	}

	fclose (f);
}

void Con_ClearNotify (void) {
	int		i;

	for (i=0 ; i<NUM_CON_TIMES ; i++)
		con.times[i] = 0;
}

void Con_MessageMode_f (void) {
	chat_team = false;
	cls.key_dest = key_message;
}

void Con_MessageMode2_f (void) {
	chat_team = true;
	cls.key_dest = key_message;
}

/*
 * If the line width has changed, reformat the buffer.
 */
void Con_CheckResize (void) {
	int		i, j, width, oldwidth, oldtotallines, numlines, numchars;
	char	tbuf[CON_TEXTSIZE];

	width = (viddef.width >> 3) - 2;

	if (width == con.linewidth)
		return;

	/* video hasn't been initialized yet */
	if (width < 1) {
		width = 38;
		con.linewidth = width;
		con.totallines = CON_TEXTSIZE / con.linewidth;
		memset (con.text, ' ', CON_TEXTSIZE);

	} else {
		oldwidth = con.linewidth;
		con.linewidth = width;
		oldtotallines = con.totallines;
		con.totallines = CON_TEXTSIZE / con.linewidth;
		numlines = oldtotallines;

		if (con.totallines < numlines)
			numlines = con.totallines;

		numchars = oldwidth;

		if (con.linewidth < numchars)
			numchars = con.linewidth;

		memcpy (tbuf, con.text, CON_TEXTSIZE);
		memset (con.text, ' ', CON_TEXTSIZE);

		for (i=0 ; i<numlines ; i++) {
			for (j=0 ; j<numchars ; j++) {
				con.text[(con.totallines - 1 - i) * con.linewidth + j] =
				    tbuf[((con.current - i + oldtotallines) %
				          oldtotallines) * oldwidth + j];
			}
		}

		Con_ClearNotify ();
	}

	con.current = con.totallines - 1;
	con.display = con.current;
}

void Con_Init (void) {
	con.linewidth = -1;

	Con_CheckResize ();

	Com_Printf ("Console initialized.\n");

	/* register our commands */
	con_notifytime = Cvar_Get ("con_notifytime", "3", 0);

	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
	Cmd_AddCommand ("togglechat", Con_ToggleChat_f);
	Cmd_AddCommand ("messagemode", Con_MessageMode_f);
	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
	Cmd_AddCommand ("clear", Con_Clear_f);
	Cmd_AddCommand ("condump", Con_Dump_f);
	con.initialized = true;
}

void Con_Linefeed (void) {
	con.x = 0;

	if (con.display == con.current)
		con.display++;

	con.current++;
	memset (&con.text[(con.current%con.totallines)*con.linewidth]
	        , ' ', con.linewidth);
}

/*
 * Handles cursor positioning, line wrapping, etc All console printing
 * must go through this in order to be logged to disk If no console is
 * visible, the text will appear at the top of the game window
 */
void Con_Print (char *txt) {
	int		y;
	int		c, l;
	static int	cr;
	int		mask;

	if (!con.initialized)
		return;

	if (txt[0] == 1 || txt[0] == 2) {
		mask = 128;		/* go to colored text */
		txt++;

	} else
		mask = 0;


	while ( (c = *txt) ) {
		/* count word length */
		for (l=0 ; l< con.linewidth ; l++)
			if ( txt[l] <= ' ')
				break;

		/* word wrap */
		if (l != con.linewidth && (con.x + l > con.linewidth) )
			con.x = 0;

		txt++;

		if (cr) {
			con.current--;
			cr = false;
		}


		if (!con.x) {
			Con_Linefeed ();

			/* mark time for transparent overlay */
			if (con.current >= 0)
				con.times[con.current % NUM_CON_TIMES] = cls.realtime;
		}

		switch (c) {
			case '\n':
				con.x = 0;
				break;

			case '\r':
				con.x = 0;
				cr = 1;
				break;

			default: /* display character and advance */
				y = con.current % con.totallines;
				con.text[y*con.linewidth+con.x] = c | mask | con.ormask;
				con.x++;

				if (con.x >= con.linewidth)
					con.x = 0;

				break;
		}

	}
}

void Con_CenteredPrint (char *text) {
	int		l;
	char	buffer[1024];

	l = strlen(text);
	l = (con.linewidth-l)/2;

	if (l < 0)
		l = 0;

	memset (buffer, ' ', l);
	strcpy (buffer+l, text);
	strcat (buffer, "\n");
	Con_Print (buffer);
}

/*
 * The input line scrolls horizontally if 
 * typing goes beyond the right edge
 */
void Con_DrawInput (void) {
	int		y;
	int		i;
	char	*text;

	if (cls.key_dest == key_menu)
		return;

	/* don't draw anything (always draw if not active) */
	if (cls.key_dest != key_console && cls.state == ca_active)
		return;

	text = key_lines[edit_line];

	/* add the cursor frame */
	text[key_linepos] = 10+((int)(cls.realtime>>8)&1);

	/* fill out remainder with spaces */
	for (i=key_linepos+1 ; i< con.linewidth ; i++)
		text[i] = ' ';

	/* prestep if horizontally scrolling */
	if (key_linepos >= con.linewidth)
		text += 1 + key_linepos - con.linewidth;

	/* draw it */
	y = con.vislines-16;

	for (i=0 ; i<con.linewidth ; i++)
		re.DrawChar ( (i+1)<<3, con.vislines - 22, text[i]);

	/* remove cursor */
	key_lines[edit_line][key_linepos] = 0;
}


/*
 * Draws the last few lines of output transparently over the game top
 */
void Con_DrawNotify (void) {
	int		x, v;
	char	*text;
	int		i;
	int		time;
	char	*s;
	int		skip;

	v = 0;

	for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++) {
		if (i < 0)
			continue;

		time = con.times[i % NUM_CON_TIMES];

		if (time == 0)
			continue;

		time = cls.realtime - time;

		if (time > con_notifytime->value*1000)
			continue;

		text = con.text + (i % con.totallines)*con.linewidth;

		for (x = 0 ; x < con.linewidth ; x++)
			re.DrawChar ( (x+1)<<3, v, text[x]);

		v += 8;
	}

	if (cls.key_dest == key_message) {
		if (chat_team) {
			DrawString (8, v, "say_team:");
			skip = 11;

		} else {
			DrawString (8, v, "say:");
			skip = 5;
		}

		s = chat_buffer;

		if (chat_bufferlen > (viddef.width>>3)-(skip+1))
			s += chat_bufferlen - ((viddef.width>>3)-(skip+1));

		x = 0;

		while(s[x]) {
			re.DrawChar ( (x+skip)<<3, v, s[x]);
			x++;
		}

		re.DrawChar ( (x+skip)<<3, v, 10+((cls.realtime>>8)&1));
		v += 8;
	}

	if (v) {
		SCR_AddDirtyPoint (0,0);
		SCR_AddDirtyPoint (viddef.width-1, v);
	}
}

/*
 * Draws the console with the solid background
 */
void Con_DrawConsole (float frac) {
	int				i, j, x, y, n;
	int				rows;
	char			*text;
	int				row;
	int				lines;
	char			version[48];
	char			dlbar[1024];
	char			timebuf[48];
	char			tmpbuf[48];

	time_t			t;
	struct tm		*today;

	lines = viddef.height * frac;

	if (lines <= 0)
		return;

	if (lines > viddef.height)
		lines = viddef.height;

	/* draw the background */
	re.DrawStretchPic (0, -viddef.height+lines, viddef.width, viddef.height, "conback");
	SCR_AddDirtyPoint (0,0);
	SCR_AddDirtyPoint (viddef.width-1,lines-1);

	Com_sprintf (version, sizeof(version), "Yamagi Quake II v%4.2f",VERSION);

	for (x=0 ; x<21 ; x++)
		re.DrawChar (viddef.width-173+x*8, lines-35, 128 + version[x] );

	t = time(NULL);
	today = localtime(&t);
	strftime (timebuf, sizeof(timebuf), "%H:%M:%S - %m/%d/%Y", today);

	Com_sprintf (tmpbuf, sizeof(tmpbuf), "%s",timebuf);

	for (x=0 ; x<21 ; x++)
		re.DrawChar (viddef.width-173+x*8, lines-25, 128 + tmpbuf[x] );

	/* draw the text */
	con.vislines = lines;

	rows = (lines-22)>>3; /* rows of text to draw */

	y = lines - 30;

	/* draw from the bottom up */
	if (con.display != con.current) {
		/* draw arrows to show the buffer is backscrolled */
		for (x=0 ; x<con.linewidth ; x+=4)
			re.DrawChar ( (x+1)<<3, y, '^');

		y -= 8;
		rows--;
	}

	row = con.display;

	for (i=0 ; i<rows ; i++, y-=8, row--) {
		if (row < 0)
			break;

		if (con.current - row >= con.totallines)
			break; /* past scrollback wrap point */

		text = con.text + (row % con.totallines)*con.linewidth;

		for (x=0 ; x<con.linewidth ; x++)
			re.DrawChar ( (x+1)<<3, y, text[x]);
	}

	/* draw the download bar, figure out width */
	if (cls.download) {
		if ((text = strrchr(cls.downloadname, '/')) != NULL)
			text++;

		else
			text = cls.downloadname;

		x = con.linewidth - ((con.linewidth * 7) / 40);
		y = x - strlen(text) - 8;
		i = con.linewidth/3;

		if (strlen(text) > i) {
			y = x - i - 11;
			strncpy(dlbar, text, i);
			dlbar[i] = 0;
			strcat(dlbar, "...");

		} else
			strcpy(dlbar, text);

		strcat(dlbar, ": ");
		i = strlen(dlbar);
		dlbar[i++] = '\x80';

		/* where's the dot gone? */
		if (cls.downloadpercent == 0)
			n = 0;

		else
			n = y * cls.downloadpercent / 100;

		for (j = 0; j < y; j++)
			if (j == n)
				dlbar[i++] = '\x83';

			else
				dlbar[i++] = '\x81';

		dlbar[i++] = '\x82';
		dlbar[i] = 0;

		sprintf(dlbar + strlen(dlbar), " %02d%%", cls.downloadpercent);

		/* draw it */
		y = con.vislines-12;

		for (i = 0; i < strlen(dlbar); i++)
			re.DrawChar ( (i+1)<<3, y, dlbar[i]);
	}

	/* draw the input prompt, user text, and cursor if desired */
	Con_DrawInput ();
}

